package client

import (
	"chess_platform/controllers"
	"chess_platform/internal/alisdk"
	. "chess_platform/internal/common"
	"chess_platform/internal/token"
	"chess_platform/models/client"
	"encoding/base64"
	"fmt"
	"time"
)

//[client]日志模块
type LogController struct {
	BaseController
}

func (b *LogController) URLMapping() {
	b.Mapping("NewError", b.NewError)
	b.Mapping("NewErrorLog", b.NewErrorLog)
	b.Mapping("SubmitLog", b.SubmitLog)
}

//如果没有token,就把uid改为-1
func (b *LogController) Prepare() {
	_, actionName := b.GetControllerAndAction()
	if actionName != "NewErrorLog" {
		LogClient.Debug("用户输入:" + string(b.Ctx.Input.RequestBody))
	} else {
		LogClient.Debug("用户访问接口是:" + b.Ctx.Input.URL() + "数据量大，所以不展示出用户的输入")
	}
	LogClient.Debug("用户访问url:" + b.Ctx.Input.URL())

	b.Res = new(controllers.Response)
	var t controllers.Token
	//如果在url后面带的没找到,就要http body里找
	t.Token = b.GetString("token")
	if t.Token == "" {
		err := b.Unmarshal(&t)
		if err != nil {
			b.DecodeParamsError(err)
			return
		}
	}
	ex, err := token.Parse(t.Token)
	if t.Token == "" {
		//token为空，说明还没登录之前就挂了
		b.Uid = -1
		LogClient.Debug("用户Id:%d", b.Uid)
		keyAccessLimit := fmt.Sprintf("ClientUserLimit-%v", b.Uid)
		if !AccessLimit(keyAccessLimit, Al.Frequency, Al.ExpireTime) {
			b.ResponseFail("操作过于频繁，请稍候再操作")
			return
		}
		return
	}
	if err != nil || ex.GetTokenType() != TokenClient {
		LogClient.Error(fmt.Sprintf("token解析错误:%v,或不是此Client类型的token", err))
		b.Res.Code = ErrUserUnauthorized
		b.Res.Msg = MsgErrUserUnauthorized
		b.Response()
		return
	}

	b.Uid = ex.GetId()
	LogClient.Debug("用户Id:%d", b.Uid)

	info, _ := ex.GetInfo().(float64)
	b.InitialLoginToday = int64(info)

	if ex.IsExpire() {
		b.Res.Code = ErrUserUnauthorized
		b.Res.Msg = MsgErrUserExpire
		b.Response()
		return
	}

	/*
		//cache里不存在说明token已经过期了
		key := fmt.Sprintf("%v%v", KeyClientTokenCache, b.Uid)
		if ex.IsExpire() || !RedisCache.IsExist(key) {
			b.Res.Code = ErrUserUnauthorized
			b.Res.Msg = MsgErrUserExpire
			b.Response()
			return
		}

		//cache里的token与传进来的不一样的话表示此帐号在其它地方登录了
		tokenCache := RedisCache.GetString(key)
		if tokenCache != "" { //token在缓存里存在
			if tokenCache != t.Token {
				b.Res.Code = ErrClientAlreadyLogin
				b.Res.Msg = MsgErrAlreadyLogin
				b.Response()
				return
			}
		} else { //不存在
			b.Res.Code = ErrUserUnauthorized
			b.Res.Msg = MsgErrUserUnauthorized
			b.Response()
			return
		}
	*/
}

// @Title 新建错误日志
// @Description 新建错误日志
// @Param   msg  	     body    string  true      "错误日志信息"
// @Param   device_id  	 body    string  true      "设备id"
// @Param   model   	 body    string  true      "设备型号"
// @Param   version_id 	 body    string  true      "大厅软件版本号"
// @Param   hot_version  body    string  false     "热更软件版本号"
// @Success 0 {string} 状态码
// @Failure 400
// @Failure 404
// @router /new-error [post]
func (b *LogController) NewError() {
	go func() {
		if b.Uid != -1 {
			//控制速度，因为如果一个用户频繁上报，岂不是被搞死
			keyAccessLimit := fmt.Sprintf("CUELimit-%v", b.Uid)
			if !AccessLimit(keyAccessLimit, 10, 60) { //一分钟内最多接收10次
				LogClient.Warning(fmt.Sprintf("用户(ID:%v)堆栈日志上报过于频繁，请稍候再操作", b.Uid))
				//	b.ResponseSuccess("操作过于频繁，请稍候再操作")
				return
			}
		}
		var p Error
		if !b.DecodeParams(&p) {
			return
		}
		if !b.ValidParams(&p) {
			return
		}
		if b.Uid == -1 {
			//控制速度，因为如果一个用户频繁上报，岂不是被搞死
			keyAccessLimit := fmt.Sprintf("CUELimit-%v", p.DeviceId)
			if !AccessLimit(keyAccessLimit, 10, 60) { //一分钟内最多接收10次
				LogClient.Warning(fmt.Sprintf("用户(ID:%v)堆栈日志上报过于频繁，请稍候再操作", b.Uid))
				//	b.ResponseSuccess("操作过于频繁，请稍候再操作")
				return
			}
		}

		msg, _ := base64.StdEncoding.DecodeString(p.Msg)
		data := map[string]interface{}{
			"uid":         b.Uid,
			"error_time":  time.Now().Unix(),
			"msg":         string(msg),
			"ip":          b.Ctx.Input.IP(),
			"device_id":   p.DeviceId,
			"model":       p.Model,
			"version_id":  p.VersionId,
			"hot_version": p.HotVersion,
			"md5":         Md5sum(p.Msg),
		}
		//不用关心数据库写时是否出错了
		_, err := client.NewError(data)
		if err != nil {
			LogClient.Error(fmt.Sprintf("堆栈日志写入失败:%v", err))
		}
	}()

	b.ResponseSuccess()
}

type SubmitLog struct {
	Msg        string `json:"msg"`
	Status     int64  `json:"status"`
	Mod        int64  `json:"mod"`
	DeviceId   string `json:"device_id"`
	Model      string `json:"model"`
	VersionId  string `json:"version_id"`
	HotVersion string `json:"hot_version"`
}

// @Title 上报打点日志
// @Description 上报打点日志
// @Param   msg  	     body    string  true      "打点错误日志信息"
// @Param   status	     body    int     true      "状态"
// @Param   mod	         body    int     true      "模块"
// @Param   device_id  	 body    string  true      "设备id"
// @Param   model   	 body    string  true      "设备型号"
// @Param   version_id 	 body    string  true      "大厅软件版本号"
// @Param   hot_version  body    string  true     "热更软件版本号"
// @Success 0 {string} 状态码
// @Failure 400
// @Failure 404
// @router /submit-log [post]
func (b *LogController) SubmitLog() {
	go func() {
		//控制速度，因为如果一个用户频繁上报，岂不是被搞死
		if b.Uid != -1 {
			keyAccessLimit := fmt.Sprintf("SubmitLogLimit-%v", b.Uid)
			if !AccessLimit(keyAccessLimit, 200, 60) { //一分钟内最多接收200次
				//如果在60秒内达到了200次,则未来10分钟提交的打点日志信息会直接拒绝
				MemCache.Expire(keyAccessLimit, 600) //10分钟
				client.UpdateSubmitLogInvalid(b.Uid, 60)
				LogClient.Warning(fmt.Sprintf("用户(ID:%v)打点日志上报过于频繁，请稍候再操作", b.Uid))
				//b.ResponseSuccess("操作过于频繁，请稍候再操作")
				return
			}
		}

		var p SubmitLog
		if !b.DecodeParams(&p) {
			return
		}
		if !b.ValidParams(&p) {
			return
		}
		if b.Uid == -1 {
			keyAccessLimit := fmt.Sprintf("SubmitLogLimit-%v", p.DeviceId)
			if !AccessLimit(keyAccessLimit, 200, 60) { //一分钟内最多接收200次
				//如果在60秒内达到了200次,则未来10分钟提交的打点日志信息会直接拒绝
				MemCache.Expire(keyAccessLimit, 600) //10分钟
				client.UpdateSubmitLogInvalid(b.Uid, 60)
				LogClient.Warning(fmt.Sprintf("用户(ID:%v)打点日志上报过于频繁，请稍候再操作", b.Uid))
				//b.ResponseSuccess("操作过于频繁，请稍候再操作")
				return
			}
		}
		data := map[string]interface{}{
			"uid":         b.Uid,
			"error_time":  time.Now().Unix(),
			"msg":         p.Msg,
			"status":      p.Status,
			"mod":         p.Mod,
			"model":       p.Model,
			"device_id":   p.DeviceId,
			"version_id":  p.VersionId,
			"hot_version": p.HotVersion,
		}
		//不用关心数据库写时是否出错了
		_, err := client.NewSubmitLog(data)
		if err != nil {
			LogClient.Error(fmt.Sprintf("打点日志写入失败:%v", err))
		}
	}()
	b.ResponseSuccess()
}

// @Title 新建错误日志(手动提交代码)
// @Description 新建错误日志(手动提交代码)
// @Param   msg  	        body    string  false     "错误日志信息(用户填写的,可不填)"
// @Param   model   	 	body    string  true      "设备型号"
// @Param   device_id  	 	body    string  true      "设备id"
// @Param   app_version 	body    string  true      "软件版本号(大厅的版本)"
// @Param   game_version 	body    string  true      "游戏版本号"
// @Param   file        	body    string  true      "操作日志文件"
// @Success 0 {string} 状态码
// @Failure 400
// @Failure 404
// @router /new-error-log [post]
func (b *LogController) NewErrorLog() {
	//控制速度，因为如果一个用户频繁上报，岂不是被搞死
	keyAccessLimit := fmt.Sprintf("CUELogLimit-%v", b.Uid)
	if !AccessLimit(keyAccessLimit, 10, 60) { //一分钟内最多接收10次
		b.ResponseSuccess("操作过于频繁，请稍候再操作")
		return
	}
	var p ErrorLog
	p.Msg = b.GetString("msg")
	p.Model = b.GetString("model")
	p.DeviceId = b.GetString("device_id")
	p.AppVersion = b.GetString("app_version")
	p.GameVersion = b.GetString("game_version")
	go func() {
		filename := fmt.Sprintf("log_%v_%v.txt", time.Now().Unix(), b.Uid)
		savePath := fmt.Sprintf("/client_error/%v", time.Now().Format("2006-01-02")) //错误日志记录
		ossPath, err := alisdk.UploadFileToOss(b.Ctx.Request.Body, filename, PlatformRunMode+savePath, b.Uid)
		if err != nil {
			LogClient.Error(b.SystemError(err.Error()))
			return
		}
		day := time.Now().Unix() - 604800 //604800 = 7 * 24 * 3600
		url, err := client.GetDeleteErrorLog(day)
		if err != nil {
			LogClient.Error(fmt.Sprintf("获取删除过期错误日志失败:%v", err))
		}
		if url != "" {
			err = alisdk.DelFileFromOSS(url)
			if err != nil {
				LogClient.Error(fmt.Sprintf("删除存储在OSS的过期错误日志失败:%v", err))
			}
		}

		data := map[string]interface{}{
			"uid":          b.Uid,
			"msg":          p.Msg,
			"model":        p.Model,
			"device_id":    p.DeviceId,
			"ip":           b.Ctx.Input.IP(),
			"app_version":  p.AppVersion,
			"game_version": p.GameVersion,
			"log_url":      ossPath,
			"error_time":   time.Now().Unix(),
		}
		//不用关心数据库写时是否出错了
		_, err = client.NewErrorLog(data)
		if err != nil {
			LogClient.Error(b.SystemError(err.Error()))
			return
		}
	}()
	b.ResponseSuccess()
}
